﻿using System;
using System.Collections.Generic;
using System.Globalization;
using System.IO;
using System.Linq;
using System.Net;
using System.Text;
using System.Text.RegularExpressions;
using System.Xml.Serialization;
using AgileDirect.SPDToolkit.Workflows.Foundation.WebpartService;
using Microsoft.SharePoint;
using Microsoft.SharePoint.Workflow;

namespace AgileDirect.SPDToolkit.Workflows.Foundation
{
    /// <summary>
    /// List info entities
    /// </summary>
    public class ListInfo
    {
        public string Title { get; set; }

        public string Description { get; set; }

        public Guid ID { get; set; }
    }

    /// <summary>
    /// Utility class for exporting and importing workflows
    /// </summary>
    public static class SPDWorkflowManager
    {
        /// <summary>
        /// Imports and associates all workflows in given directory
        /// </summary>
        public static void ImportWorkflowInformation(string workflowName, string targetWebUrl, string exportDir)
        {
            var exportedListsResult = LoadExportListInformation(exportDir);

            List<ListInfo> newListInfo;
            using (var site = new SPSite(targetWebUrl))
            {
                using (var web = site.OpenWeb())
                {
                    EnsureWorkflowLists(web);
                    newListInfo = AnalyzeListExportInformation(web);
                }
            }

            using (var site = new SPSite(targetWebUrl))
            {
                using (var web = site.OpenWeb())
                {
                    AddWorkflowsToLibrary(web, workflowName, exportDir, exportedListsResult, newListInfo);
                }
            }
        }

        /// <summary>
        /// Export the workflow information to the filesystem
        /// </summary>
        public static void ExportWorkflowInformation(string workflowName, string sourceWebUrl, string exportDir)
        {
            using (var site = new SPSite(sourceWebUrl))
            {
                using (var web = site.OpenWeb())
                {
                    ExportWorkflowFiles(web, workflowName, exportDir);

                    var exportedListInfo = AnalyzeListExportInformation(web);
                    SaveListExportInformation(exportedListInfo, exportDir);
                }
            }
        }

        /// <summary>
        /// Creates the necessary tasks and workflow libraries if not exists
        /// </summary>
        private static void EnsureWorkflowLists(SPWeb web)
        {
            try
            {
                var list = web.Lists["Workflows"];
            }
            catch (Exception)
            {
                Console.WriteLine("Creating workflows list..");
                web.Lists.Add("Workflows", string.Empty, SPListTemplateType.NoCodeWorkflows);
            }

            try
            {
                var list = web.Lists["Tasks"];
            }
            catch (Exception)
            {
                Console.WriteLine("Creating tasks list..");
                web.Lists.Add("Tasks", string.Empty, SPListTemplateType.Tasks);
            }
        }

        /// <summary>
        /// Adds the necessary files to the library
        /// </summary>
        private static void AddWorkflowsToLibrary(SPWeb web, string name,
                                                  string exportDir, List<ListInfo> exportedListInfo,
                                                  List<ListInfo> newListInfo)
        {
            SPList workflowLibrary = web.Lists["Workflows"];
            if (workflowLibrary != null)
            {
                var folder = workflowLibrary.RootFolder.SubFolders.Add(name);
                folder.Update();

                foreach (string file in Directory.GetFiles(exportDir))
                {
                    var contents = File.ReadAllText(file);

                    //Only replace if we are workflow content
                    if (file.Contains(".xoml"))
                    {
                        contents = ReplaceIdsInContent(contents, exportedListInfo,
                                                       newListInfo);
                    }

                    Console.WriteLine("Adding " + Path.GetFileName(file));
                    folder.Files.Add(Path.GetFileName(file), Encoding.UTF8.GetBytes(contents));
                }

                SPFileCollection collection = folder.Files;

                var xomlFile = FindFileByName(collection, name + ".xoml");
                var xomlConfigFile = FindFileByName(collection, name + ".xoml.wfconfig.xml");
                var rulesFile = FindFileByName(collection, name + ".xoml.rules");

                //Get the information
                string xomlContents = GetStringFromFile(web, xomlFile);
                string configContents = GetStringFromFile(web, xomlConfigFile);

                //Some workflows dont have rules
                string rulesContents = null;
                if (rulesFile != null)
                {
                    rulesContents = GetStringFromFile(web, rulesFile);
                }

                Console.WriteLine("Updating version information in config xml");

                //Create new config file
                configContents = UpdateVersionsInConfig(xomlFile.UIVersionLabel,
                                                        rulesFile != null ? rulesFile.UIVersionLabel : null,
                                                        configContents);

                folder.Files.Add(xomlConfigFile.Url,
                                 Encoding.UTF8.GetBytes(configContents), true);

                //Reload the new config
                xomlConfigFile = FindFileByName(collection, name + ".xoml.wfconfig.xml");

                //Validate the workflow
                var service = new WebPartPagesWebService();
                service.Url = String.Format("{0}/{1}", web.Url, "/_vti_bin/webpartpages.asmx");
                service.PreAuthenticate = true;
                service.Credentials = CredentialCache.DefaultCredentials;

                //Todo: Refactor to no webservice version
                Console.WriteLine("Validating workflow markup");
                service.ValidateWorkflowMarkupAndCreateSupportObjects(xomlContents, rulesContents, configContents, "2");

                //Todo: Refactor to no webservice version
                Console.WriteLine("Associating workflow");
                service.AssociateWorkflowMarkup(xomlConfigFile.Url, "V" + xomlConfigFile.UIVersionLabel);
            }
        }

        /// <summary>
        /// Get contents of an file 
        /// </summary>
        private static string GetStringFromFile(SPWeb web, SPFile file)
        {
            return web.GetFileAsString(file.Url);
        }

        /// <summary>
        /// Find an file by its name
        /// </summary>
        private static SPFile FindFileByName(SPFileCollection collection, string p)
        {
            foreach (SPFile file in collection)
            {
                if (file.Name == p)
                    return file;
            }
            return null;
        }

        /// <summary>
        /// Replace the config files
        /// </summary>
        private static string UpdateVersionsInConfig(string xomlVersion, string rulesVersion, string config)
        {
            config = Regex.Replace(config, "XomlVersion=\"V(.*?)\"", string.Format("XomlVersion=\"V{0}\"", xomlVersion));
            if (rulesVersion != null)
            {
                config = Regex.Replace(config, "RulesVersion=\"V(.*?)\"",
                       string.Format("RulesVersion=\"V{0}\"", rulesVersion));
            }
            return config;
        }

        /// <summary>
        /// Read exported list information
        /// </summary>
        private static List<ListInfo> LoadExportListInformation(string exportPath)
        {
            ListInfo[] list;
            var serializer = new XmlSerializer(typeof(ListInfo[]));
            using (Stream stream = File.OpenRead(exportPath + "ExportedListInformation.xml"))
            {
                list = serializer.Deserialize(stream) as ListInfo[];
            }
            return list != null ? new List<ListInfo>(list) : null;
        }

        /// <summary>
        /// Export necessary list information for replace
        /// </summary>
        private static void SaveListExportInformation(List<ListInfo> listInfo, string exportPath)
        {
            var serializer = new XmlSerializer(typeof(ListInfo[]));
            using (var stream = File.Create(exportPath + "ExportedListInformation.xml"))
            {
                serializer.Serialize(stream, listInfo.ToArray());
            }
        }

        /// <summary>
        /// Replace the ids in the content
        /// </summary>
        private static string ReplaceIdsInContent(string contents, 
            List<ListInfo> exportedListInfo,
            List<ListInfo> newListInfo)
        {
            var regex =
                new Regex(@"(\{){0,1}[0-9a-fA-F]{8}\-[0-9a-fA-F]{4}\-[0-9a-fA-F]{4}\-[0-9a-fA-F]{4}\-[0-9a-fA-F]{12}(\}){0,1}");

            return regex.Replace(contents, delegate(Match match)
               {
                   var guid = new Guid(match.Groups[0].Value);

                   //Find the old guid from the exported list
                   ListInfo foundInfo = FindByID(exportedListInfo, guid);
                   if (foundInfo != null)
                   {
                       //Use the title to lookup in new list
                       Console.WriteLine("Found list dependency {0} ({1})",
                                         foundInfo.Title, foundInfo.ID);
                       ListInfo newInfo = FindByTitle(newListInfo, foundInfo.Title);
                       if (newInfo != null)
                       {
                           //If found use the new Id
                           return String.Format("{{{0}}}", newInfo.ID);
                       }
                       //Not found in new list, return the old id
                       return String.Format("{{{0}}}", foundInfo.ID);
                   }

                   //Not found in orignal list, return the guid
                   return String.Format("{{{0}}}", guid);
               });
        }

        /// <summary>
        /// Find by title
        /// </summary>
        private static ListInfo FindByTitle(List<ListInfo> newListInfo, string title)
        {
            return (from a in newListInfo where a.Title == title select a).FirstOrDefault();
        }

        /// <summary>
        /// Finds info by guid
        /// </summary>
        private static ListInfo FindByID(List<ListInfo> listInfo, Guid guid)
        {
            return (from a in listInfo where a.ID == guid select a).FirstOrDefault();
        }

        /// <summary>
        /// Export list information
        /// </summary>
        private static List<ListInfo> AnalyzeListExportInformation(SPWeb web)
        {
            var items = new List<ListInfo>();
            foreach (SPList list in web.Lists)
            {
                items.Add(new ListInfo { ID = list.ID, Title = list.Title, Description = list.Description });
            }
            return items;
        }

        /// <summary>
        /// Export the files
        /// </summary>
        private static void ExportWorkflowFiles(SPWeb web, string name, string path)
        {
            var wfList = web.Lists["Workflows"];
            var folder = web.GetFolder(string.Format("Workflows/{0}", name));

            var query = new SPQuery();
            query.Folder = folder;

            var items = wfList.GetItems(query);
            foreach (SPListItem item in items)
            {
                string content = web.GetFileAsString(item.Url);

                File.WriteAllText(path + item.Name, content);
            }
        }

        /// <summary>
        /// Set wheter the workflow is allowed to create 
        /// </summary>
        public static void SetWorkflowAssociationEnabled(string url, string listname,
             string workflowname, bool status)
        {
            using (var site = new SPSite(url))
            {
                using (SPWeb web = site.OpenWeb())
                {
                    var list = web.Lists[listname];
                    var association = web.Lists[listname].WorkflowAssociations.
                        GetAssociationByName(workflowname, CultureInfo.InvariantCulture);

                    association.Enabled = status;
                    list.UpdateWorkflowAssociation(association);
                }
            }
        }

        /// <summary>
        /// Removes an workflow association from an list
        /// </summary>
        public static void RemoveWorkflowAssociation(string url, string listname, string workflowname)
        {
            using (var site = new SPSite(url))
            {
                using (SPWeb web = site.OpenWeb())
                {
                    SPList list = web.Lists[listname];
                    SPWorkflowAssociation association =
                        web.Lists[listname].WorkflowAssociations.GetAssociationByName(workflowname,
                                                                                      CultureInfo.InvariantCulture);

                    list.RemoveWorkflowAssociation(association);
                }
            }
        }

        /// <summary>
        /// Associates an workflow with an sharepoint list
        /// </summary>
        public static void AddWorkflowAssociation(string url, string workflow, string listname)
        {
            using (var site = new SPSite(url))
            {
                using (SPWeb web = site.OpenWeb())
                {
                    //TODO
                }
            }
        }

        /// <summary>
        /// Exports all workflows from given site
        /// </summary>
        public static void ExportAllWorkflowInformation(string sourceWebUrl, string exportDir)
        {
            using (var site = new SPSite(sourceWebUrl))
            {
                using (SPWeb web = site.OpenWeb())
                {
                    SPList workflows = web.Lists["Workflows"];
                    foreach (SPFolder folder in workflows.RootFolder.SubFolders)
                    {
                        if (folder.Name != "Forms")
                        {
                            string workflowExportDir = Path.Combine(exportDir, folder.Name + "\\");
                            EnsureDirectory(workflowExportDir);

                            ExportWorkflowFiles(web, folder.Name, workflowExportDir);

                            var exportedListInfo = AnalyzeListExportInformation(web);
                            SaveListExportInformation(exportedListInfo, workflowExportDir);
                        }
                    }
                }
            }
        }

        /// <summary>
        /// Ensures that an directory exists
        /// </summary>
        private static void EnsureDirectory(string workflowExportDir)
        {
            if (!Directory.Exists(workflowExportDir))
            {
                Directory.CreateDirectory(workflowExportDir);
            }
        }
    }
}